/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package com.feilong.lib.javassist.compiler;

class Token{

    public Token  next = null;

    public int    tokenId;

    public long   longValue;

    public double doubleValue;

    public String textValue;
}

public class Lex implements TokenId{

    private int          lastChar;

    private StringBuffer textBuffer;

    private Token        currentToken;

    private Token        lookAheadTokens;

    private String       input;

    @SuppressWarnings("unused")
    private int          position, maxlen, lineNumber;

    /**
     * Constructs a lexical analyzer.
     */
    public Lex(String s){
        lastChar = -1;
        textBuffer = new StringBuffer();
        currentToken = new Token();
        lookAheadTokens = null;

        input = s;
        position = 0;
        maxlen = s.length();
        lineNumber = 0;
    }

    public int get(){
        if (lookAheadTokens == null){
            return get(currentToken);
        }
        Token t;
        currentToken = t = lookAheadTokens;
        lookAheadTokens = lookAheadTokens.next;
        return t.tokenId;
    }

    /**
     * Looks at the next token.
     */
    public int lookAhead(){
        return lookAhead(0);
    }

    public int lookAhead(int i){
        Token tk = lookAheadTokens;
        if (tk == null){
            lookAheadTokens = tk = currentToken; // reuse an object!
            tk.next = null;
            get(tk);
        }

        for (; i-- > 0; tk = tk.next){
            if (tk.next == null){
                Token tk2;
                tk.next = tk2 = new Token();
                get(tk2);
            }
        }

        currentToken = tk;
        return tk.tokenId;
    }

    public String getString(){
        return currentToken.textValue;
    }

    public long getLong(){
        return currentToken.longValue;
    }

    public double getDouble(){
        return currentToken.doubleValue;
    }

    private int get(Token token){
        int t;
        do{
            t = readLine(token);
        }while (t == '\n');
        token.tokenId = t;
        return t;
    }

    private int readLine(Token token){
        int c = getNextNonWhiteChar();
        if (c < 0){
            return c;
        }else if (c == '\n'){
            ++lineNumber;
            return '\n';
        }else if (c == '\''){
            return readCharConst(token);
        }else if (c == '"'){
            return readStringL(token);
        }else if ('0' <= c && c <= '9'){
            return readNumber(c, token);
        }else if (c == '.'){
            c = getc();
            if ('0' <= c && c <= '9'){
                StringBuffer tbuf = textBuffer;
                tbuf.setLength(0);
                tbuf.append('.');
                return readDouble(tbuf, c, token);
            }
            ungetc(c);
            return readSeparator('.');
        }else if (Character.isJavaIdentifierStart((char) c)){
            return readIdentifier(c, token);
        }
        return readSeparator(c);
    }

    private int getNextNonWhiteChar(){
        int c;
        do{
            c = getc();
            if (c == '/'){
                c = getc();
                if (c == '/'){
                    do{
                        c = getc();
                    }while (c != '\n' && c != '\r' && c != -1);
                }else if (c == '*'){
                    while (true){
                        c = getc();
                        if (c == -1){
                            break;
                        }else if (c == '*'){
                            if ((c = getc()) == '/'){
                                c = ' ';
                                break;
                            }else{
                                ungetc(c);
                            }
                        }
                    }
                }else{
                    ungetc(c);
                    c = '/';
                }
            }
        }while (isBlank(c));
        return c;
    }

    private int readCharConst(Token token){
        int c;
        int value = 0;
        while ((c = getc()) != '\''){
            if (c == '\\'){
                value = readEscapeChar();
            }else if (c < 0x20){
                if (c == '\n'){
                    ++lineNumber;
                }

                return BadToken;
            }else{
                value = c;
            }
        }

        token.longValue = value;
        return CharConstant;
    }

    private int readEscapeChar(){
        int c = getc();
        if (c == 'n'){
            c = '\n';
        }else if (c == 't'){
            c = '\t';
        }else if (c == 'r'){
            c = '\r';
        }else if (c == 'f'){
            c = '\f';
        }else if (c == '\n'){
            ++lineNumber;
        }

        return c;
    }

    private int readStringL(Token token){
        int c;
        StringBuffer tbuf = textBuffer;
        tbuf.setLength(0);
        for (;;){
            while ((c = getc()) != '"'){
                if (c == '\\'){
                    c = readEscapeChar();
                }else if (c == '\n' || c < 0){
                    ++lineNumber;
                    return BadToken;
                }

                tbuf.append((char) c);
            }

            for (;;){
                c = getc();
                if (c == '\n'){
                    ++lineNumber;
                }else if (!isBlank(c)){
                    break;
                }
            }

            if (c != '"'){
                ungetc(c);
                break;
            }
        }

        token.textValue = tbuf.toString();
        return StringL;
    }

    private int readNumber(int c,Token token){
        long value = 0;
        int c2 = getc();
        if (c == '0'){
            if (c2 == 'X' || c2 == 'x'){
                for (;;){
                    c = getc();
                    if ('0' <= c && c <= '9'){
                        value = value * 16 + (c - '0');
                    }else if ('A' <= c && c <= 'F'){
                        value = value * 16 + (c - 'A' + 10);
                    }else if ('a' <= c && c <= 'f'){
                        value = value * 16 + (c - 'a' + 10);
                    }else{
                        token.longValue = value;
                        if (c == 'L' || c == 'l'){
                            return LongConstant;
                        }
                        ungetc(c);
                        return IntConstant;
                    }
                }
            }else if ('0' <= c2 && c2 <= '7'){
                value = c2 - '0';
                for (;;){
                    c = getc();
                    if ('0' <= c && c <= '7'){
                        value = value * 8 + (c - '0');
                    }else{
                        token.longValue = value;
                        if (c == 'L' || c == 'l'){
                            return LongConstant;
                        }
                        ungetc(c);
                        return IntConstant;
                    }
                }
            }
        }

        value = c - '0';
        while ('0' <= c2 && c2 <= '9'){
            value = value * 10 + c2 - '0';
            c2 = getc();
        }

        token.longValue = value;
        if (c2 == 'F' || c2 == 'f'){
            token.doubleValue = value;
            return FloatConstant;
        }else if (c2 == 'E' || c2 == 'e' || c2 == 'D' || c2 == 'd' || c2 == '.'){
            StringBuffer tbuf = textBuffer;
            tbuf.setLength(0);
            tbuf.append(value);
            return readDouble(tbuf, c2, token);
        }else if (c2 == 'L' || c2 == 'l'){
            return LongConstant;
        }else{
            ungetc(c2);
            return IntConstant;
        }
    }

    private int readDouble(StringBuffer sbuf,int c,Token token){
        if (c != 'E' && c != 'e' && c != 'D' && c != 'd'){
            sbuf.append((char) c);
            for (;;){
                c = getc();
                if ('0' <= c && c <= '9'){
                    sbuf.append((char) c);
                }else{
                    break;
                }
            }
        }

        if (c == 'E' || c == 'e'){
            sbuf.append((char) c);
            c = getc();
            if (c == '+' || c == '-'){
                sbuf.append((char) c);
                c = getc();
            }

            while ('0' <= c && c <= '9'){
                sbuf.append((char) c);
                c = getc();
            }
        }

        try{
            token.doubleValue = Double.parseDouble(sbuf.toString());
        }catch (NumberFormatException e){
            return BadToken;
        }

        if (c == 'F' || c == 'f'){
            return FloatConstant;
        }
        if (c != 'D' && c != 'd'){
            ungetc(c);
        }

        return DoubleConstant;
    }

    // !"#$%&'(    )*+,-./0    12345678    9:;<=>?
    private static final int[] equalOps = {
                                            NEQ,
                                            0,
                                            0,
                                            0,
                                            MOD_E,
                                            AND_E,
                                            0,
                                            0,
                                            0,
                                            MUL_E,
                                            PLUS_E,
                                            0,
                                            MINUS_E,
                                            0,
                                            DIV_E,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            LE,
                                            EQ,
                                            GE,
                                            0 };

    private int readSeparator(int c){
        int c2, c3;
        if ('!' <= c && c <= '?'){
            int t = equalOps[c - '!'];
            if (t == 0){
                return c;
            }
            c2 = getc();
            if (c == c2){
                switch (c) {
                    case '=':
                        return EQ;
                    case '+':
                        return PLUSPLUS;
                    case '-':
                        return MINUSMINUS;
                    case '&':
                        return ANDAND;
                    case '<':
                        c3 = getc();
                        if (c3 == '='){
                            return LSHIFT_E;
                        }
                        ungetc(c3);
                        return LSHIFT;
                    case '>':
                        c3 = getc();
                        if (c3 == '='){
                            return RSHIFT_E;
                        }else if (c3 == '>'){
                            c3 = getc();
                            if (c3 == '='){
                                return ARSHIFT_E;
                            }
                            ungetc(c3);
                            return ARSHIFT;
                        }else{
                            ungetc(c3);
                            return RSHIFT;
                        }
                    default:
                        break;
                }
            }else if (c2 == '='){
                return t;
            }
        }else if (c == '^'){
            c2 = getc();
            if (c2 == '='){
                return EXOR_E;
            }
        }else if (c == '|'){
            c2 = getc();
            if (c2 == '='){
                return OR_E;
            }else if (c2 == '|'){
                return OROR;
            }
        }else{
            return c;
        }

        ungetc(c2);
        return c;
    }

    private int readIdentifier(int c,Token token){
        StringBuffer tbuf = textBuffer;
        tbuf.setLength(0);

        do{
            tbuf.append((char) c);
            c = getc();
        }while (Character.isJavaIdentifierPart((char) c));

        ungetc(c);

        String name = tbuf.toString();
        int t = ktable.lookup(name);
        if (t >= 0){
            return t;
        }
        /*
         * tbuf.toString() is executed quickly since it does not
         * need memory copy. Using a hand-written extensible
         * byte-array class instead of StringBuffer is not a good idea
         * for execution speed. Converting a byte array to a String
         * object is very slow. Using an extensible char array
         * might be OK.
         */
        token.textValue = name;
        return Identifier;
    }

    private static final KeywordTable ktable = new KeywordTable();

    static{
        ktable.append("abstract", ABSTRACT);
        ktable.append("boolean", BOOLEAN);
        ktable.append("break", BREAK);
        ktable.append("byte", BYTE);
        ktable.append("case", CASE);
        ktable.append("catch", CATCH);
        ktable.append("char", CHAR);
        ktable.append("class", CLASS);
        ktable.append("const", CONST);
        ktable.append("continue", CONTINUE);
        ktable.append("default", DEFAULT);
        ktable.append("do", DO);
        ktable.append("double", DOUBLE);
        ktable.append("else", ELSE);
        ktable.append("extends", EXTENDS);
        ktable.append("false", FALSE);
        ktable.append("final", FINAL);
        ktable.append("finally", FINALLY);
        ktable.append("float", FLOAT);
        ktable.append("for", FOR);
        ktable.append("goto", GOTO);
        ktable.append("if", IF);
        ktable.append("implements", IMPLEMENTS);
        ktable.append("import", IMPORT);
        ktable.append("instanceof", INSTANCEOF);
        ktable.append("int", INT);
        ktable.append("interface", INTERFACE);
        ktable.append("long", LONG);
        ktable.append("native", NATIVE);
        ktable.append("new", NEW);
        ktable.append("null", NULL);
        ktable.append("package", PACKAGE);
        ktable.append("private", PRIVATE);
        ktable.append("protected", PROTECTED);
        ktable.append("public", PUBLIC);
        ktable.append("return", RETURN);
        ktable.append("short", SHORT);
        ktable.append("static", STATIC);
        ktable.append("strictfp", STRICT);
        ktable.append("super", SUPER);
        ktable.append("switch", SWITCH);
        ktable.append("synchronized", SYNCHRONIZED);
        ktable.append("this", THIS);
        ktable.append("throw", THROW);
        ktable.append("throws", THROWS);
        ktable.append("transient", TRANSIENT);
        ktable.append("true", TRUE);
        ktable.append("try", TRY);
        ktable.append("void", VOID);
        ktable.append("volatile", VOLATILE);
        ktable.append("while", WHILE);
    }

    private static boolean isBlank(int c){
        return c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\n';
    }

    @SuppressWarnings("unused")
    private static boolean isDigit(int c){
        return '0' <= c && c <= '9';
    }

    private void ungetc(int c){
        lastChar = c;
    }

    public String getTextAround(){
        int begin = position - 10;
        if (begin < 0){
            begin = 0;
        }

        int end = position + 10;
        if (end > maxlen){
            end = maxlen;
        }

        return input.substring(begin, end);
    }

    private int getc(){
        if (lastChar < 0){
            if (position < maxlen){
                return input.charAt(position++);
            }else{
                return -1;
            }
        }
        int c = lastChar;
        lastChar = -1;
        return c;
    }
}
